<?php
if (!defined('APP_ID')) { die('No direct script access allowed'); }

/**
 * Access Control List (Acl) library
 */
class Acl {
	private static $_instance = null;
	private $_roles = array();
	private $_rules = array();

    public static function &instance() {
        if (!isset(self::$_instance)) {
			$c = __CLASS__;
            self::$_instance = new $c;
        }

        return self::$_instance;
    }
	
    public function __clone() {
		throw new Node_Exception('Can`t clone singletone object');
	}

	/**
	 * Loading roles and permissions
	 */
	private function __construct() {
		Config::load('acl');

		// Roles
		foreach (Config::get('roles', 'acl') as $value) {
			$this->addRole($value);
		}

		// Rules
		foreach (Config::get('rules', 'acl') as $type => $rules) {
			foreach ($rules as $role => $rule) {
				$this->_parseRule($role, $rule, $type);
			}
		}
	}
	
	/**
	 * Add role
	 */
	public function addRole($role) {
		$role = trim(strtolower($role));
		if (!in_array($role, $this->_roles)) {
			$this->_roles[] = $role;
		}
	}

	/**
	 * Allow access
	 */
	public function allow($role = null, $controller = null, $action = null) {
		$this->applyRule($this->createRule($role, $controller, $action, 'allow'));
	}

	/**
	 * Deny access
	 */
	public function deny($role = null, $controller = null, $action = null) {
		$this->applyRule($this->createRule($role, $controller, $action, 'deny'));
	}

	/**
	 * Check access
	 */
	public function check($role, $controller, $action) {
		$result = false;
		
		foreach ($this->_rules as $rule) {
			if (is_null($rule['role']) && is_null($rule['controller']) && is_null($rule['action'])) {
				if ($rule['permission'] == 'allow') {
					$result = true;
				} else {
					$result = false;
				}
			}
		}

		foreach ($this->_rules as $rule) {
			if (($rule['role'] == $role) && is_null($rule['controller']) && is_null($rule['action'])) {
				if ($rule['permission'] == 'allow') {
					$result = true;
				} else {
					$result = false;
				}
			}
		}

		foreach ($this->_rules as $rule) {
			if (($rule['role'] == $role) 
			    && ($rule['controller'] == $controller)
			    && (is_null($rule['action']) || ($rule['action'] == $action))
			) {
				if ($rule['permission'] == 'allow') {
					$result = true;
				} else {
					$result = false;
				}
			}
		}
		
		return $result;
	}
	
	/**
	 * Creatre rules
	 */
	private function createRule($role = null, $controller = null, $action = null, $permission = 'deny') {
		$rule = array();

		$rule = array(
			'role' => null,
			'controller' => null,
			'action' => null,
			'permission' => $permission
		);

		$rule['role'] = $role;
		$rule['controller'] = $controller;
		$rule['action'] = $action;
		$rule['permission'] = $permission;
		
		return $rule;
	}
	
	/**
	 * Applying rules
	 */
	private function applyRule($new_rule) {
		$found = false;
		
		foreach ($this->_rules as &$rule) {
			if (($rule['role'] == $new_rule['role'])
			     && ($rule['controller'] == $new_rule['controller'])
			     && ($rule['action'] == $new_rule['action'])
			) {
				$rule['permission'] = $new_rule['permission'];
				$found = true;
				break;
			}
		}
		
		if (!$found) {
			$this->_rules[] = $new_rule;
		}
	}
	
	/**
	 * Dumping rules
	 */
	public function dump() {
		print_r($this->_rules);
	}
	
	/**
	 * Parsing rule
	 */
	private function _parseRule($role, $rule, $type) {
		$controller = null;
		$action = null;
		if ($role == '*') {
			$role = null;
		}
		
		$rules = explode(',', $rule);
		foreach ($rules as $single_rule) {
			if (empty($single_rule)) { continue; }
			$single_rule_arr = explode(':', $single_rule);
			
			$controller = null;
			if (isset($single_rule_arr[0]) && !empty($single_rule_arr[0])) {
				$controller = $single_rule_arr[0];
			}

			if ($controller == '*') {
				$controller = null;
			}

			$action = null;
			if (isset($single_rule_arr[1]) && !empty($single_rule_arr[1])) {
				$action = $single_rule_arr[1];
			}
			
			if ($action == '*') {
				$action = null;
			}
			
			$this->applyRule($this->createRule($role, $controller, $action, $type));
		}
	}
}
